// **********************************************************************
// 
// <copyright>
// 
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
// 
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
// 
// </copyright>
// **********************************************************************
// 
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/BufferedLayerMapBean.java,v $
// $RCSfile: BufferedLayerMapBean.java,v $
// $Revision: 1.6 $
// $Date: 2004/10/14 18:05:39 $
// $Author: dietrick $
// 
// **********************************************************************

package com.bbn.openmap;

import java.awt.Color;
import java.awt.Component;
import java.awt.Paint;
import java.awt.event.ContainerEvent;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.bbn.openmap.event.LayerEvent;
import com.bbn.openmap.layer.BufferedLayer;

/**
 * The BufferedLayerMapBean is a BufferedMapBean with an additional image buffer
 * that holds Layers designated as background layers. The additional image
 * buffer is a BufferedLayer that this MapBean manages, and all background
 * layers are added to the BufferedLayer, which is automatically added to the
 * bottom of the map. When layers are added to the MapBean via the setLayers()
 * method, the Layer.getAddAsBackground() flag is checked, and if that is true
 * for a layer, it is added to the BufferedLayer. The background layers do not
 * receive mouse events.
 * <P>
 * 
 * It should be cautioned that the appearance of the map may not match the layer
 * stack as it is delivered to the MapBean because of this flag. If, for
 * example, layers 1 and 4 are marked as background layers, while layers 2 and 3
 * are not (in a 4 layer stack), then the map will show layers 2, 3, 1, 4, with
 * layers 1 and 4 being displayed from the BufferedLayer. Something to think
 * about when it comes to designing GUI elements.
 */
public class BufferedLayerMapBean extends BufferedMapBean {

    private static Logger logger = Logger.getLogger(BufferedLayerMapBean.class.getName());
    protected BufferedLayer bufferedLayer;

    protected boolean DEBUG = false;

    /**
     * Construct a MapBean.
     */
    public BufferedLayerMapBean() {
        super();
        DEBUG = logger.isLoggable(Level.FINE);
    }

    public BufferedLayerMapBean(boolean useThreadedNotification) {
        super(useThreadedNotification);
        DEBUG = logger.isLoggable(Level.FINE);
    }

    /**
     * Set the background color of the map. Actually sets the background color
     * of the projection, and calls repaint().
     * 
     * @param color java.awt.Color.
     */
    public void setBackgroundColor(Color color) {
        super.setBackground(color);
        getBufferedLayer().setBackground(color);
    }

    public void setBckgrnd(Paint paint) {
        super.setBckgrnd(paint);
        getBufferedLayer().setBckgrnd(paint);
    }

    public synchronized void setBufferedLayer(BufferedLayer bl) {
        bufferedLayer = bl;
    }

    public synchronized BufferedLayer getBufferedLayer() {
        if (bufferedLayer == null) {
            bufferedLayer = new BufferedLayer();
            addPropertyChangeListener(bufferedLayer);
            bufferedLayer.setName("Background Layers");
        }

        return bufferedLayer;
    }

    /**
     * Set the MapBeanRepaintPolicy used by the MapBean. This method is
     * overridden in order to pass the policy on to the MapBean stored in the
     * internal BufferedLayer.
     */
    public void setMapBeanRepaintPolicy(MapBeanRepaintPolicy mbrp) {
        super.setMapBeanRepaintPolicy(mbrp);

        MapBean mb = getBufferedLayer().getMapBean();
        if (mb != null) {
            if (mbrp == null) {
                mb.setMapBeanRepaintPolicy(mbrp);
            } else {
                MapBeanRepaintPolicy mbrp2 = (MapBeanRepaintPolicy) mbrp.clone();
                mb.setMapBeanRepaintPolicy(mbrp2);
                mbrp2.setMap(mb);
            }
        }
    }

    /**
     * LayerListener interface method. A list of layers will be added, removed,
     * or replaced based on on the type of LayerEvent.
     * 
     * @param evt a LayerEvent
     */
    public void setLayers(LayerEvent evt) {
        setBufferDirty(true);
        Layer[] layers = evt.getLayers();
        int type = evt.getType();

        if (type == LayerEvent.ALL) {
            // Don't care about these at all...
            return;
        }

        // @HACK is this cool?:
        if (layers == null) {
            logger.warning("layer[] is null!");
            return;
        }

        boolean oldChange = getDoContainerChange();
        setDoContainerChange(false);

        BufferedLayer bufLayer;

        synchronized (this) {
            bufLayer = getBufferedLayer();
        }

        // use LayerEvent.REPLACE when you want to remove all current
        // layers add a new set
        if (type == LayerEvent.REPLACE) {
            if (DEBUG) {
                debugmsg("Replacing all layers");
            }

            removeAll();
            bufLayer.clearLayers();
            bufLayer.setProjection(getRotatedProjection());

            for (Layer layer : layers) {
                // @HACK is this cool?:
                if (layer == null) {
                    logger.warning("skipping null layer in layer array passed to MapBean");
                    continue;
                }

                if (DEBUG) {
                    debugmsg("Adding layer[" + layer.getName() + "]");
                }

                if (layer.getAddAsBackground()) {
                    if (DEBUG) {
                        logger.fine("Adding layer[" + layer.getName() + "] to background");
                    }

                    bufLayer.addLayer(layer);
                } else {
                    add(layer);
                }

                layer.setVisible(true);
            }

            if (bufLayer.hasLayers()) {
                add(bufLayer);
            }
        }

        // use LayerEvent.ADD when adding and/or reshuffling layers
        else if (type == LayerEvent.ADD) {

            remove(bufLayer);

            if (DEBUG) {
                debugmsg("Adding new layers");
            }
            for (Layer layer : layers) {
                if (DEBUG) {
                    debugmsg("Adding layer[" + layer.getName() + "]");
                }

                layer.setVisible(true);

                if (layer.getAddAsBackground()) {
                    if (DEBUG) {
                        debugmsg("Adding layer[" + layer.getName() + "] to background");
                    }
                    bufLayer.addLayer(layer);
                } else {
                    add(layer);
                }
            }

            if (bufLayer.hasLayers()) {
                add(bufLayer);
            }
        }

        // use LayerEvent.REMOVE when you want to delete layers from
        // the map
        else if (type == LayerEvent.REMOVE) {
            if (DEBUG) {
                debugmsg("Removing layers");
            }
            for (Layer layer : layers) {
                if (DEBUG) {
                    debugmsg("Removing layer[" + layer.getName() + "]");
                }
                remove(layer);
                bufLayer.removeLayer(layer);
            }
        }

        if (!layerRemovalDelayed) {
            purgeAndNotifyRemovedLayers();
        }

        setDoContainerChange(oldChange);
        revalidate();
        repaint();
    }

    /**
     * In an effort to limit map flashing, the BufferedLayerMapBean consults the
     * BufferedLayer to check that all background layers are ready to be painted
     * after a projection change, before forwarding on all repaint requests.
     */
    public void repaint(Layer layer) {
        if (bufferedLayer == null || bufferedLayer.isReadyToPaint()) {
            super.repaint(layer);
        }
    }

    /**
     * ContainerListener Interface method. Should not be called directly. Part
     * of the ContainerListener interface, and it's here to make the MapBean a
     * good Container citizen.
     * 
     * @param e ContainerEvent
     */
    protected void changeLayers(ContainerEvent e) {
        // Container Changes can be disabled to speed adding/removing
        // multiple layers
        if (!doContainerChange) {
            return;
        }

        Component[] comps = this.getComponents();
        int ncomponents = comps.length;
        int nBufLayerComponents = 0;

        BufferedLayer bufLayer;
        synchronized (this) {
            bufLayer = getBufferedLayer();
        }

        if (ncomponents == 0 || comps[ncomponents - 1] != bufLayer) {
            super.changeLayers(e);
            return;
        }

        Component[] bufLayers = bufLayer.getLayers();
        nBufLayerComponents = bufLayers.length;

        // Take 1 off for the bufLayer
        Layer[] newLayers = new Layer[ncomponents + nBufLayerComponents - 1];
        System.arraycopy(comps, 0, newLayers, 0, ncomponents - 1);
        System.arraycopy(bufLayers, 0, newLayers, ncomponents - 1, nBufLayerComponents);

        if (DEBUG) {
            debugmsg("changeLayers() - firing change");
        }

        firePropertyChange(LayersProperty, currentLayers, newLayers);

        // Tell the new layers that they have been added
        for (Layer layer : addedLayers) {
            layer.added(this);
        }
        addedLayers.removeAllElements();

        currentLayers = newLayers;
    }

    /**
     * Call when getting rid of the MapBean, it releases pointers to all
     * listeners and kills the ProjectionSupport thread.
     */
    public void dispose() {
        if (bufferedLayer != null) {
            bufferedLayer.dispose();
        }
        super.dispose();
    }
}